﻿using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Xnaml.Native;

namespace Xnaml {

	public partial class GameElement : Window {
		
		#region Fields
		[System.Diagnostics.DebuggerBrowsable( System.Diagnostics.DebuggerBrowsableState.Never )]
		private bool doneRun;
		[System.Diagnostics.DebuggerBrowsable( System.Diagnostics.DebuggerBrowsableState.Never )]
		private Game game;
		[System.Diagnostics.DebuggerBrowsable( System.Diagnostics.DebuggerBrowsableState.Never )]
		private Window toplevel;
		[System.Diagnostics.DebuggerBrowsable( System.Diagnostics.DebuggerBrowsableState.Never )]
		internal Window front;
		
		protected static internal readonly Point WindowsBorderSize = new Point(8, 8);
		protected static internal readonly Point WindowsBorderSizeTop = new Point( 8, 30 ); 

		private bool mousefocus = false, invisiblefromresize = false;

		#endregion Fields

		#region Properties

		internal object XAMLHost {
			get {
				return front.Content;
			}
			set {
				front.Content = value;
			}
		}

		internal double TrueWidth {
			get {
				if ( WindowState == WindowState.Maximized )
					return Width;
				else
					return ActualWidth;
			}
		}

		internal double TrueHeight {
			get {
				if ( WindowState == WindowState.Maximized )
					return Height;
				else
					return ActualHeight;
			}
		}

		internal Window TopLevelWindow {
			get {
				if ( toplevel != Window.GetWindow( game ) ) {
					toplevel = Window.GetWindow( game );
					Owner = toplevel;
					toplevel.Closing -= new System.ComponentModel.CancelEventHandler( TopLevel_Closing );
					toplevel.Closing += new System.ComponentModel.CancelEventHandler( TopLevel_Closing );
					toplevel.Closed -= new EventHandler( TopLevel_Closed );
					toplevel.Closed += new EventHandler( TopLevel_Closed );
					toplevel.LocationChanged -= new EventHandler( TopLevel_LocationChanged );
					toplevel.LocationChanged += new EventHandler( TopLevel_LocationChanged );
					toplevel.SizeChanged -= new SizeChangedEventHandler( TopLevel_SizeChanged );
					toplevel.SizeChanged += new SizeChangedEventHandler( TopLevel_SizeChanged );
					UpdateBounds();
				}
				return toplevel;
			}
		}

		public bool IsMouseFocused {
			get {
				return mousefocus;
			}
		}

		public IntPtr Handle {
			get {
				return new System.Windows.Interop.WindowInteropHelper( this ).Handle;
			}
		}

		#endregion Properties

		#region Constructors

		public GameElement ( Game gameelement ) {
			game = gameelement;
			HookElementEvents();
			LockThreadToProcessor();
			Width = Height = 1;
			IsHitTestVisible = true;
			ResizeMode = ResizeMode.NoResize;
			ShowInTaskbar = false;
			WindowState = WindowState.Normal;
			WindowStyle = WindowStyle.None;
			Background = new SolidColorBrush( Color.FromArgb( 0, 255, 255, 255 ) );
			//AllowsTransparency = true;

			front = new Window();
			front.IsHitTestVisible = true;
			front.Width = front.Height = 1;
			front.ResizeMode = ResizeMode.NoResize;
			front.ShowInTaskbar = false;
			front.WindowState = WindowState.Normal;
			front.WindowStyle = WindowStyle.None;
			front.AllowsTransparency = true;
			front.Background = new SolidColorBrush( Color.FromArgb( 0, 255, 255, 255 ) );
		}

		#endregion Constructors

		#region Events

		public event EventHandler<EventArgs> WindowResize;
		public event EventHandler<EventArgs> WindowLayout;

		private void HookElementEvents ( ) {
			game.SizeChanged += new SizeChangedEventHandler( Game_SizeChanged );
			game.LayoutUpdated += new EventHandler( Game_LayoutUpdated );
			game.IsVisibleChanged += new DependencyPropertyChangedEventHandler( Game_IsVisibleChanged );
		}

		private void TopLevel_LocationChanged ( object sender, EventArgs e ) {
			UpdateBounds();
		}

		private void TopLevel_SizeChanged ( object sender, SizeChangedEventArgs e ) {
			UpdateBounds();
		}

		private void TopLevel_Closing ( object sender, System.ComponentModel.CancelEventArgs e ) {
			Owner = null;
		}

		private void TopLevel_Closed ( object sender, EventArgs e ) {
			game.Exit();
		}

		private void Game_LayoutUpdated ( object sender, EventArgs e ) {
			UpdateBounds();
			if ( WindowLayout != null )
				WindowLayout( this, EventArgs.Empty );
		}

		private void Game_SizeChanged ( object sender, SizeChangedEventArgs e ) {
			UpdateBounds();
			if ( WindowResize != null )
				WindowResize( this, EventArgs.Empty );
		}

		private void Game_IsVisibleChanged ( object sender, DependencyPropertyChangedEventArgs e ) {
			if ( (bool)e.NewValue ) {
				if ( !IsActive ) {
					Show();
					if ( front.Owner != this )
						front.Owner = this;
					front.Show();
				}
			}
			else {
				Hide();
				front.Hide();
			}
		}

		protected override void OnGotMouseCapture ( MouseEventArgs e ) {
			mousefocus = true;
			base.OnGotMouseCapture( e );
		}

		protected override void OnMouseDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			mousefocus = true;
			Microsoft.Xna.Framework.Input.Mouse.WindowHandle = Handle;
			game.iOnMouseDown( e );
			base.OnMouseDown( e );
		}

		protected override void OnMouseUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			mousefocus = true;
			Microsoft.Xna.Framework.Input.Mouse.WindowHandle = Handle;
			game.iOnMouseUp( e );
			base.OnMouseUp( e );
		}

		protected override void OnMouseEnter ( System.Windows.Input.MouseEventArgs e ) {
			mousefocus = true;
			Microsoft.Xna.Framework.Input.Mouse.WindowHandle = Handle;
			game.iOnMouseEnter( e );
			base.OnMouseEnter( e );
		}

		protected override void OnMouseLeave ( System.Windows.Input.MouseEventArgs e ) {
			mousefocus = false;
			game.iOnMouseLeave( e );
			base.OnMouseLeave( e );
		}

		protected override void OnMouseWheel ( System.Windows.Input.MouseWheelEventArgs e ) {
			game.iOnMouseWheel( e );
			base.OnMouseWheel( e );
		}

		protected override void OnMouseMove ( System.Windows.Input.MouseEventArgs e ) {
			game.iOnMouseMove( e );
			base.OnMouseMove( e );
		}

		protected override void OnMouseLeftButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnMouseLeftButtonDown( e );
			base.OnMouseLeftButtonDown( e );
		}

		protected override void OnMouseRightButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnMouseRightButtonDown( e );
			base.OnMouseRightButtonDown( e );
		}

		protected override void OnMouseLeftButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnMouseLeftButtonUp( e );
			base.OnMouseLeftButtonUp( e );
		}

		protected override void OnMouseRightButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnMouseRightButtonUp( e );
			base.OnMouseRightButtonUp( e );
		}

		protected override void OnPreviewMouseDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnPreviewMouseDown( e );
			base.OnPreviewMouseDown( e );
		}

		protected override void OnPreviewMouseUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnPreviewMouseUp( e );
			base.OnPreviewMouseUp( e );
		}

		protected override void OnPreviewMouseWheel ( System.Windows.Input.MouseWheelEventArgs e ) {
			game.iOnPreviewMouseWheel( e );
			base.OnPreviewMouseWheel( e );
		}

		protected override void OnPreviewMouseMove ( System.Windows.Input.MouseEventArgs e ) {
			game.iOnPreviewMouseMove( e );
			base.OnPreviewMouseMove( e );
		}

		protected override void OnPreviewMouseLeftButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnPreviewMouseLeftButtonDown( e );
			base.OnPreviewMouseLeftButtonDown( e );
		}

		protected override void OnPreviewMouseRightButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnPreviewMouseLeftButtonDown( e );
			base.OnPreviewMouseLeftButtonDown( e );
		}

		protected override void OnPreviewMouseLeftButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnPreviewMouseLeftButtonUp( e );
			base.OnPreviewMouseLeftButtonUp( e );
		}

		protected override void OnPreviewMouseRightButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			game.iOnPreviewMouseRightButtonUp( e );
			base.OnPreviewMouseRightButtonUp( e );
		}

		protected override void OnKeyDown ( KeyEventArgs e ) {
			game.iOnKeyDown( e );
			base.OnKeyDown( e );
		}

		protected override void OnKeyUp ( KeyEventArgs e ) {
			game.iOnKeyUp( e );
			base.OnKeyUp( e );
		}

		protected override void OnGotKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			game.iOnGotKeyboardFocus( e );
			base.OnGotKeyboardFocus( e );
		}

		protected override void OnLostKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			game.iOnLostKeyboardFocus( e );
			base.OnLostKeyboardFocus( e );
		}

		protected override void OnPreviewKeyDown ( KeyEventArgs e ) {
			game.iOnPreviewKeyDown( e );
			base.OnPreviewKeyDown( e );
		}

		protected override void OnPreviewKeyUp ( KeyEventArgs e ) {
			game.iOnPreviewKeyUp( e );
			base.OnPreviewKeyUp( e );
		}

		protected override void OnPreviewGotKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			game.iOnPreviewGotKeyboardFocus( e );
			base.OnPreviewGotKeyboardFocus( e );
		}

		protected override void OnPreviewLostKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			game.iOnPreviewLostKeyboardFocus( e );
			base.OnPreviewLostKeyboardFocus( e );
		}

		#endregion Events

		#region Visibility Methods
		internal void SizeHide ( ) {
			invisiblefromresize = true;
			//front.Hide( );
			Hide( );
		}
		#endregion

		#region Public methods

		internal void UpdateBounds ( ) {
			if ( IsVisible || (!IsVisible && invisiblefromresize) ) {
				Point topleft = game.TransformToVisual( TopLevelWindow ).Transform( new Point( -2, -5 ) );

				if ( TopLevelWindow.WindowStyle != WindowStyle.None ) {
					topleft.X += WindowsBorderSizeTop.X;
					topleft.Y += WindowsBorderSizeTop.Y;
				}

				if ( TopLevelWindow.WindowState != WindowState.Maximized ) {
					topleft.X += TopLevelWindow.Left;
					topleft.Y += TopLevelWindow.Top;
				}

				Left = front.Left = topleft.X;
				Top = front.Top = topleft.Y;
				
			}
			
			if ( !IsVisible && invisiblefromresize ) {
				if ( TopLevelWindow.Left + TopLevelWindow.Width - WindowsBorderSizeTop.X - WindowsBorderSize.X > Left &&
					TopLevelWindow.Top + TopLevelWindow.Height - WindowsBorderSizeTop.Y - WindowsBorderSize.Y > Top ) {
					Show( );
					invisiblefromresize = false;
					game.iOnResized( EventArgs.Empty );
				}
			}
			else if ( IsVisible ) {
				double target = 0;
				if ( front.WindowState != WindowState )
					front.WindowState = WindowState;
				if ( WindowState != WindowState.Maximized ) {
					Rect rect = new Rect( Left, Top, game.DesiredSize.Width, game.DesiredSize.Height );
					Rect toprect = new Rect( TopLevelWindow.Left, TopLevelWindow.Top, TopLevelWindow.Width, TopLevelWindow.Height );
					if ( game.ClipToWindow ) {
						if ( toprect.Width >= WindowsBorderSize.X ) {
							toprect.Width -= WindowsBorderSize.X;
						}
						if ( toprect.Height >= 8 ) {
							toprect.Height -= WindowsBorderSize.X;
						}
						if ( rect.Right > toprect.Right ) {
							target = rect.Right - toprect.Right;
							if ( rect.Width - target <= 0 ) {
								SizeHide( );
							}
							else {
								rect.Width -= target;
							}
						}
						if ( rect.Bottom > toprect.Bottom ) {
							target = rect.Bottom - toprect.Bottom;
							if ( rect.Height - target <= 0 ) {
								SizeHide( );
							}
							else {
								rect.Height -= target;
							}
						}
					}

					if ( invisiblefromresize ) {
						return;
					} 
					
					Width = front.Width = rect.Width;
					Height = front.Height = rect.Height;

				}
				game.iOnResized(EventArgs.Empty);
			}
		}

		#endregion Public methods

		#region Life methods

		private void LockThreadToProcessor ( ) {
			UIntPtr lpProcessAffinityMask = UIntPtr.Zero;
			UIntPtr lpSystemAffinityMask = UIntPtr.Zero;
			if ( Win32.GetProcessAffinityMask( Win32.GetCurrentProcess(), out lpProcessAffinityMask, out lpSystemAffinityMask ) && ( lpProcessAffinityMask != UIntPtr.Zero ) ) {
				UIntPtr dwThreadAffinityMask = (UIntPtr)( lpProcessAffinityMask.ToUInt64() & ( ~lpProcessAffinityMask.ToUInt64() + 1 ) );
				Win32.SetThreadAffinityMask( Win32.GetCurrentThread(), dwThreadAffinityMask );
			}
		}

		internal void Exit ( ) {
			front.Hide( );
			front.Close( );
			Hide( );
			Close( );
		}

		internal void PreRun ( ) {
			if ( doneRun ) {
				throw new InvalidOperationException( Properties.Resources.NoMultipleRuns );
			}
			try {
			}
			catch ( Exception ) {
				PostRun();
				throw;
			}
		}

		internal void PostRun ( ) {
			doneRun = true;
		}

		#endregion Life methods
	}
}